using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Xml;
using System.Globalization;
using OpenMetaverse;
using OpenMetaverse.Assets;
using OpenMetaverse.StructuredData;

namespace SimExport
{
	public class Linkset
	{
		public Primitive Parent;
		public List<Primitive> Children = new List<Primitive>();
	}

	public class OarFile
	{
		enum ProfileShape : byte
		{
			Circle = 0,
			Square = 1,
			IsometricTriangle = 2,
			EquilateralTriangle = 3,
			RightTriangle = 4,
			HalfCircle = 5
		}

		public static void PackageArchive(string directoryName, string filename)
		{
			const string ARCHIVE_XML = "<?xml version=\"1.0\" encoding=\"utf-16\"?>\n<archive major_version=\"0\" minor_version=\"2\" />";

			TarArchiveWriter archive = new TarArchiveWriter();

			// Create the archive.xml file
			archive.AddFile("archive.xml", ARCHIVE_XML);

			// Add the assets
			string[] files = Directory.GetFiles(directoryName + "/assets");
			foreach (string file in files)
				archive.AddFile("assets/" + Path.GetFileName(file), File.ReadAllBytes(file));

			// Add the objects
			files = Directory.GetFiles(directoryName + "/objects");
			foreach (string file in files)
				archive.AddFile("objects/" + Path.GetFileName(file), File.ReadAllBytes(file));

			// Add the terrain(s)
			files = Directory.GetFiles(directoryName + "/terrains");
			foreach (string file in files)
				archive.AddFile("terrains/" + Path.GetFileName(file), File.ReadAllBytes(file));

			// Add the settings (region info)
			files = Directory.GetFiles(directoryName + "/settings");
			foreach (string file in files)
				archive.AddFile("settings/" + Path.GetFileName(file), File.ReadAllBytes(file));

			archive.WriteTar(new GZipStream(new FileStream(filename, FileMode.Create), CompressionMode.Compress));
		}

		public static void SavePrims(DoubleDictionary<uint, UUID, Primitive> prims, string path)
		{
			// Delete all of the old linkset files
			try
			{
				Directory.Delete(path, true);
				Directory.CreateDirectory(path);
			}
			catch (Exception ex)
			{
				Logger.Log("Failed saving prims: " + ex.Message, Helpers.LogLevel.Error);
				return;
			}

			// Copy the double dictionary to a temporary list for iterating
			List<Primitive> primList = new List<Primitive>();
			prims.ForEach(delegate(Primitive prim)
			{
				try
				{
					primList.Add(prim);
				}
				catch (Exception ex)
				{
					Logger.Log("Failed adding prims: " + ex.Message, Helpers.LogLevel.Error);
					return;
				}
			});

			Logger.Log("Total prims added: " + primList.Count, Helpers.LogLevel.Info);

			int counter = 0;

			foreach (Primitive p in primList)
			{
				counter++;
				if ((counter % 100) == 0)
						Logger.Log(String.Format("Cycled through {0} of {1} prims ", counter, primList.Count), Helpers.LogLevel.Info);

				// Now run through the linkset and saeve it
				if (p.ParentID == 0)
				{
					Linkset linkset = new Linkset();
					linkset.Parent = p;

					prims.ForEach(delegate(Primitive q)
					{
						if (q.ParentID == p.LocalID)
						{
							try
							{
								linkset.Children.Add(q);
							}
							catch
							{
							}
						}
					});

					string primName = "Primitive";

					try
					{
						if (p.Properties != null)
							// may fail if we don't have the properties
							primName = p.Properties.Name;
					}
					catch(Exception ex)
					{
					}

					int x = (int)p.Position.X;
					int y = (int)p.Position.Y;
					int z = (int)p.Position.Z;

					if (linkset != null && linkset.Parent != null)
						SaveLinkset(linkset, path + "/" + SanitizePath(primName, '-') + "_" +
							x.ToString() + "-" +
							y.ToString() + "-" +
							z.ToString() + "-" +
							"__" + linkset.Parent.ID.ToString() + ".xml");
				}
			}
		}

		static void SaveLinkset(Linkset linkset, string filename)
		{
			try
			{
				using (StreamWriter stream = new StreamWriter(filename))
				{
					XmlTextWriter writer = new XmlTextWriter(stream);
					writer.Formatting = Formatting.Indented;
					SOGToXml2(writer, linkset);
					writer.Flush();
				}
  			}
			catch (Exception ex)
			{
				Logger.Log(String.Format("Failed saving linkset to {0}: {1}", filename, ex.Message), Helpers.LogLevel.Error);
			}
		}

		static void SOGToXml2(XmlTextWriter writer, Linkset linkset)
		{
			if (linkset.Parent == null)
			{
				// seriously borked! exit
				return;
			}

			writer.WriteStartElement(String.Empty, "SceneObjectGroup", String.Empty);
			SOPToXml(writer, linkset.Parent, null);
			writer.WriteStartElement(String.Empty, "OtherParts", String.Empty);

			if (linkset.Children != null)
			{
				foreach (Primitive child in linkset.Children)
				{
					try
					{
						if (child != null)	// exception should catch here, avoiding to write garbage
							SOPToXml(writer, child, linkset.Parent);
					}
					catch (Exception ex)
					{
						Logger.Log(String.Format("Failed writing prim {0} in a linkset: {1}", child.ID.ToString(), ex.Message), Helpers.LogLevel.Error);
						writer.WriteEndElement();
						continue;
					}
				}
			}

			writer.WriteEndElement();
			writer.WriteEndElement();
		}

		static void SOPToXml(XmlTextWriter writer, Primitive prim, Primitive parent)
		{
			writer.WriteStartElement("SceneObjectPart");
			writer.WriteAttributeString("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
			writer.WriteAttributeString("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");

			try
			{
				WriteUUIDGuid(writer, "CreatorID", prim.Properties.CreatorID);
				WriteUUIDGuid(writer, "FolderID", prim.Properties.FolderID);
				writer.WriteElementString("InventorySerial", prim.Properties.InventorySerial.ToString(Utils.EnUsCulture));
			}
			catch (Exception ex)
			{
				WriteUUIDGuid(writer, "CreatorID", new UUID("c89b204e-6c8e-4979-b21d-1ae62d767053"));	// BetaMaster Avatar
				WriteUUIDGuid(writer, "FolderID", new UUID("fa58d9c6-7229-4e1f-8b6b-f13254c7deb6"));	// Objects
				writer.WriteElementString("InventorySerial", "0");
			}

			writer.WriteStartElement("TaskInventory"); writer.WriteEndElement();

			writer.WriteElementString("ObjectFlags", ((int)prim.Flags).ToString(Utils.EnUsCulture));
			WriteUUIDGuid(writer, "UUID", prim.ID);
			writer.WriteElementString("LocalId", prim.LocalID.ToString(Utils.EnUsCulture));
			try
			{
				writer.WriteElementString("Name", prim.Properties.Name);
			}
			catch (Exception ex)
			{
				writer.WriteElementString("Name", prim.LocalID.ToString(Utils.EnUsCulture));
			}
			writer.WriteElementString("Material", ((int)prim.PrimData.Material).ToString(Utils.EnUsCulture));
			writer.WriteElementString("RegionHandle", prim.RegionHandle.ToString(Utils.EnUsCulture));
			writer.WriteElementString("ScriptAccessPin", "0");

			Vector3 groupPosition;
			if (parent == null)
				groupPosition = prim.Position;
			else
				groupPosition = parent.Position;

			WriteVector(writer, "GroupPosition", groupPosition);
			if (prim.ParentID == 0)
				WriteVector(writer, "OffsetPosition", Vector3.Zero);
			else
				WriteVector(writer, "OffsetPosition", prim.Position);
			WriteQuaternion(writer, "RotationOffset", prim.Rotation);
			WriteVector(writer, "Velocity", Vector3.Zero);
			WriteVector(writer, "RotationalVelocity", Vector3.Zero);
			WriteVector(writer, "AngularVelocity", prim.AngularVelocity);
			WriteVector(writer, "Acceleration", Vector3.Zero);
			try
			{
				writer.WriteElementString("Description", prim.Properties.Description);
			}
			catch(Exception ex)
			{
				writer.WriteElementString("Description", "");
			}
			writer.WriteStartElement("Color");
			writer.WriteElementString("R", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", prim.TextColor.R));
			writer.WriteElementString("G", String.Format(CultureInfo.InvariantCulture, "{0:0.######}",prim.TextColor.G));
			writer.WriteElementString("B", String.Format(CultureInfo.InvariantCulture, "{0:0.######}",prim.TextColor.B));
			writer.WriteElementString("A", String.Format(CultureInfo.InvariantCulture, "{0:0.######}",prim.TextColor.G));
			writer.WriteEndElement();
			writer.WriteElementString("Text", prim.Text);
			try
			{
				writer.WriteElementString("SitName", prim.Properties.SitName);
				writer.WriteElementString("TouchName", prim.Properties.TouchName);
			}
			catch(Exception ex)
			{
				writer.WriteElementString("SitName", "");
				writer.WriteElementString("TouchName", "");
			}
			uint linknum = 0;
			//if (parent != null)
			//	linknum = prim.LocalID - parent.LocalID;

			writer.WriteElementString("LinkNum", linknum.ToString(Utils.EnUsCulture));
			writer.WriteElementString("ClickAction", ((int)prim.ClickAction).ToString(Utils.EnUsCulture));
			writer.WriteStartElement("Shape");

			writer.WriteElementString("PathBegin", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", Primitive.PackBeginCut(prim.PrimData.PathBegin)));
			writer.WriteElementString("PathCurve", String.Format(CultureInfo.InvariantCulture, "{0:0.######}",((byte)prim.PrimData.PathCurve)));
			writer.WriteElementString("PathEnd", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", Primitive.PackEndCut(prim.PrimData.PathEnd)));
			writer.WriteElementString("PathRadiusOffset", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", Primitive.PackPathTwist(prim.PrimData.PathRadiusOffset)));
			writer.WriteElementString("PathRevolutions", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", Primitive.PackPathRevolutions(prim.PrimData.PathRevolutions)));
			writer.WriteElementString("PathScaleX", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", Primitive.PackPathScale(prim.PrimData.PathScaleX)));
			writer.WriteElementString("PathScaleY", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", Primitive.PackPathScale(prim.PrimData.PathScaleY)));
			writer.WriteElementString("PathShearX", String.Format(CultureInfo.InvariantCulture, "{0:0.######}",((byte)Primitive.PackPathShear(prim.PrimData.PathShearX))));
			writer.WriteElementString("PathShearY", String.Format(CultureInfo.InvariantCulture, "{0:0.######}",((byte)Primitive.PackPathShear(prim.PrimData.PathShearY))));
			writer.WriteElementString("PathSkew", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", Primitive.PackPathTwist(prim.PrimData.PathSkew)));
			writer.WriteElementString("PathTaperX", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", Primitive.PackPathTaper(prim.PrimData.PathTaperX)));
			writer.WriteElementString("PathTaperY", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", Primitive.PackPathTaper(prim.PrimData.PathTaperY)));
			writer.WriteElementString("PathTwist", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", Primitive.PackPathTwist(prim.PrimData.PathTwist)));
			writer.WriteElementString("PathTwistBegin", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", Primitive.PackPathTwist(prim.PrimData.PathTwistBegin)));
			writer.WriteElementString("PCode", ((byte)prim.PrimData.PCode).ToString(Utils.EnUsCulture));
			writer.WriteElementString("ProfileBegin", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", Primitive.PackBeginCut(prim.PrimData.ProfileBegin)));
			writer.WriteElementString("ProfileEnd", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", Primitive.PackEndCut(prim.PrimData.ProfileEnd)));
			writer.WriteElementString("ProfileHollow", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", Primitive.PackProfileHollow(prim.PrimData.ProfileHollow)));
			WriteVector(writer, "Scale", prim.Scale);
			writer.WriteElementString("State", prim.PrimData.State.ToString(Utils.EnUsCulture));

			ProfileShape shape = (ProfileShape)prim.PrimData.ProfileCurve;
			writer.WriteElementString("ProfileShape", shape.ToString(Utils.EnUsCulture));
			writer.WriteElementString("HollowShape", prim.PrimData.ProfileHole.ToString(Utils.EnUsCulture));
			writer.WriteElementString("ProfileCurve", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", prim.PrimData.profileCurve));

			writer.WriteStartElement("TextureEntry");

			byte[] te;
			if (prim.Textures != null)
				te = prim.Textures.GetBytes();
			else
				te = new byte[0];
				
			if (te.Length == 0) te = new byte[0];

			writer.WriteBase64(te, 0, te.Length);
			writer.WriteEndElement();

			// FIXME: ExtraParams // Attempt by Gwyn
			writer.WriteStartElement("ExtraParams");
			try
			{
				byte[] ep = prim.GetExtraParamsBytes();
				writer.WriteBase64(ep, 0, ep.Length);
			}
			catch
			{
			}
			writer.WriteEndElement(); // end ExtraParams

			// Sculpts
			if (prim.Sculpt != null)
			{
				WriteUUIDGuid(writer, "SculptTexture", prim.Sculpt.SculptTexture);
				writer.WriteElementString("SculptType", prim.Sculpt.Type.ToString());
			}
			else
			{
				WriteUUIDGuid(writer, "SculptTexture", UUID.Zero);
				writer.WriteElementString("SculptType", "0");
			}

			writer.WriteStartElement("SculptData");
			try
			{
				byte[] sd = prim.Sculpt.GetBytes();
				writer.WriteBase64(sd, 0, sd.Length);
			}
			catch
			{
			}
			writer.WriteEndElement();
			// end SculptData

			// Flexies
			try
			{
				writer.WriteElementString("FlexiSoftness", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", prim.Flexible.Softness));
				writer.WriteElementString("FlexiTension", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", prim.Flexible.Tension));
				writer.WriteElementString("FlexiDrag", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", prim.Flexible.Drag));
				writer.WriteElementString("FlexiGravity", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", prim.Flexible.Gravity));
				writer.WriteElementString("FlexiWind", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", prim.Flexible.Wind));
				writer.WriteElementString("FlexiForceX", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", prim.Flexible.Force.X));
				writer.WriteElementString("FlexiForceY", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", prim.Flexible.Force.Y));
				writer.WriteElementString("FlexiForceZ", String.Format(CultureInfo.InvariantCulture, "{0:0.######}",  prim.Flexible.Force.Z));
			}
			catch
			{
				writer.WriteElementString("FlexiSoftness", "0");
				writer.WriteElementString("FlexiTension", "0");
				writer.WriteElementString("FlexiDrag", "0");
				writer.WriteElementString("FlexiGravity", "0");
				writer.WriteElementString("FlexiWind", "0");
				writer.WriteElementString("FlexiForceX", "0");
				writer.WriteElementString("FlexiForceY", "0");
				writer.WriteElementString("FlexiForceZ", "0");
			}
			// end Flexies

			// Light
			try
			{
				writer.WriteElementString("LightColorR", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", prim.Light.Color.R));
				writer.WriteElementString("LightColorG", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", prim.Light.Color.G));
				writer.WriteElementString("LightColorB", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", prim.Light.Color.B));
				writer.WriteElementString("LightColorA", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", prim.Light.Color.A));
				writer.WriteElementString("LightRadius", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", prim.Light.Radius));
				writer.WriteElementString("LightCutOff", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", prim.Light.Cutoff));
				writer.WriteElementString("LightFalloff", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", prim.Light.Falloff));
				writer.WriteElementString("LightIntensity", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", prim.Light.Intensity));
			}
			catch
			{
				writer.WriteElementString("LightColorR", "0");
				writer.WriteElementString("LightColorG", "0");
				writer.WriteElementString("LightColorB", "0");
				writer.WriteElementString("LightColorA", "1");
				writer.WriteElementString("LightRadius", "0");
				writer.WriteElementString("LightCutOff", "0");
				writer.WriteElementString("LightFalloff", "0");
				writer.WriteElementString("LightIntensity", "1");
			}
			//end Light

			if (prim.Flexible != null)
			{
				writer.WriteElementString("FlexiEntry", "true");
			}
			else
			{
				writer.WriteElementString("FlexiEntry", "false");
			}

			if (prim.Light != null)
			{
				writer.WriteElementString("LightEntry", "true");
			}
			else
			{
				writer.WriteElementString("LightEntry", "false");
			}

			if (prim.Sculpt != null)
			{
				writer.WriteElementString("SculptEntry", "true");
			}
			else
			{
				writer.WriteElementString("SculptEntry", "false");
			}

			writer.WriteEndElement(); // end Shape

			WriteVector(writer, "Scale", prim.Scale);
			writer.WriteElementString("UpdateFlag", "0");
			WriteVector(writer, "SitTargetOrientation", Vector3.UnitZ);
			WriteVector(writer, "SitTargetPosition", Vector3.Zero);
			WriteVector(writer, "SitTargetPositionLL", Vector3.Zero);
			WriteQuaternion(writer, "SitTargetOrientationLL", new Quaternion(0f, 0f, 1f, 0f));
			writer.WriteElementString("ParentID", prim.ParentID.ToString(Utils.EnUsCulture));

			try
			{
				writer.WriteElementString("CreationDate", ((int)Utils.DateTimeToUnixTime(prim.Properties.CreationDate)).ToString(Utils.EnUsCulture));
				writer.WriteElementString("Category", ((int)prim.Properties.Category).ToString(Utils.EnUsCulture));
				writer.WriteElementString("SalePrice", prim.Properties.SalePrice.ToString(Utils.EnUsCulture));
				writer.WriteElementString("ObjectSaleType", ((int)prim.Properties.SaleType).ToString(Utils.EnUsCulture));
				writer.WriteElementString("OwnershipCost", prim.Properties.OwnershipCost.ToString(Utils.EnUsCulture));
				WriteUUIDGuid(writer, "GroupID", prim.Properties.GroupID);
				WriteUUIDGuid(writer, "OwnerID", prim.Properties.OwnerID);
				WriteUUIDGuid(writer, "LastOwnerID", prim.Properties.LastOwnerID);
			}
			catch(Exception ex)
			{
				long timeSinceEpoch = GetEpochTime();
				if (timeSinceEpoch < 0) timeSinceEpoch = 0;
				writer.WriteElementString("CreationDate", timeSinceEpoch.ToString());
				writer.WriteElementString("Category", "0");
				writer.WriteElementString("SalePrice", "0");
				writer.WriteElementString("ObjectSaleType", "0");
				writer.WriteElementString("OwnershipCost", "0");
				WriteUUIDGuid(writer, "GroupID", UUID.Zero);
				WriteUUIDGuid(writer, "OwnerID",  new UUID("c89b204e-6c8e-4979-b21d-1ae62d767053"));
				WriteUUIDGuid(writer, "LastOwnerID",  new UUID("c89b204e-6c8e-4979-b21d-1ae62d767053"));
			}
			try
			{
				writer.WriteElementString("BaseMask", ((uint)prim.Properties.Permissions.BaseMask).ToString(Utils.EnUsCulture));
				writer.WriteElementString("OwnerMask", ((uint)prim.Properties.Permissions.OwnerMask).ToString(Utils.EnUsCulture));
				writer.WriteElementString("GroupMask", ((uint)prim.Properties.Permissions.GroupMask).ToString(Utils.EnUsCulture));
				writer.WriteElementString("EveryoneMask", ((uint)prim.Properties.Permissions.EveryoneMask).ToString(Utils.EnUsCulture));
				writer.WriteElementString("NextOwnerMask", ((uint)prim.Properties.Permissions.NextOwnerMask).ToString(Utils.EnUsCulture));
			}
			catch
			{
				writer.WriteElementString("BaseMask", ((uint)PermissionMask.All).ToString(Utils.EnUsCulture));
				writer.WriteElementString("OwnerMask", ((uint)PermissionMask.All).ToString(Utils.EnUsCulture));
				writer.WriteElementString("GroupMask", ((uint)PermissionMask.All).ToString(Utils.EnUsCulture));
				writer.WriteElementString("EveryoneMask", ((uint)PermissionMask.All).ToString(Utils.EnUsCulture));
				writer.WriteElementString("NextOwnerMask", ((uint)PermissionMask.All).ToString(Utils.EnUsCulture));
			}
			writer.WriteElementString("Flags", "None");				// not retrieved by libopenmetaverse
			WriteUUIDGuid(writer, "CollisionSound", UUID.Zero);		// not retrieved by libopenmetaverse
			writer.WriteElementString("CollisionSoundVolume", "0");	// not retrieved by libopenmetaverse
			// WriteUUID(writer, "SitTargetAvatar", UUID.Zero);		// obsolete?

			writer.WriteEndElement();
		}

		static void WriteUUID(XmlTextWriter writer, string name, UUID id)
		{
			writer.WriteStartElement(name);
			writer.WriteElementString("UUID", id.ToString());
			writer.WriteEndElement();
		}

		static void WriteUUIDGuid(XmlTextWriter writer, string name, UUID id)
		{
			writer.WriteStartElement(name);
			writer.WriteElementString("Guid", id.ToString());
			writer.WriteEndElement();
		}

		static void WriteVector(XmlTextWriter writer, string name, Vector3 vec)
		{
			writer.WriteStartElement(name);
			writer.WriteElementString("X", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", vec.X));
			writer.WriteElementString("Y", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", vec.Y));
			writer.WriteElementString("Z", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", vec.Z));
			writer.WriteEndElement();
		}

		static void WriteQuaternion(XmlTextWriter writer, string name, Quaternion quat)
		{
			writer.WriteStartElement(name);
			writer.WriteElementString("X", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", quat.X));
			writer.WriteElementString("Y", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", quat.Y));
			writer.WriteElementString("Z", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", quat.Z));
			writer.WriteElementString("W", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", quat.W));
			writer.WriteEndElement();
		}

		public static string SanitizePath(string filename, char replaceChar)
		{		
			var sb = new System.Text.StringBuilder();
			
			// add '/' as invalid char, apparently it was left here
			char[] InvalidFileNameChars = new char[] {'/', '\'', '"', '<', '>', '|', ',', '.'}; 
			
			for (int i = 0; i < filename.Length; i++)
			{
				char filenameChar = filename[i];
				foreach (char c in InvalidFileNameChars)
					if (filenameChar.Equals(c))
					{
						filenameChar = replaceChar;
						break;
					}

				sb.Append(filenameChar);
			}

			return sb.ToString();
		}
		
		public static long GetEpochTime() 
		{ 
			DateTime dtCurTime = DateTime.Now; 
			DateTime dtEpochStartTime = Convert.ToDateTime("1/1/1970 8:00:00 AM"); 
			TimeSpan ts = dtCurTime.Subtract(dtEpochStartTime); 
			
			long epochtime; 
			epochtime = ((((((ts.Days * 24) + ts.Hours) * 60) + ts.Minutes) * 60) + ts.Seconds); 
			return epochtime; 
		} 

		/* Compliance with OAR format 0.2
		   Saving region info
		*/
		public static void SaveRegionInfo(Simulator sim, string path, string dir)
		{
			// Delete all of the old linkset files
			try
			{
				Directory.Delete(path + dir, true);
				Directory.CreateDirectory(path + dir);
			}
			catch (Exception ex)
			{
				Logger.Log("Failed creating " + path + dir + " directory: " + ex.Message, Helpers.LogLevel.Error);
				return;
			}

			string simName = path;
			if (sim.Name != null)
				simName = sim.Name;

			try
			{
				using (StreamWriter stream = new StreamWriter(path + dir + "/" + SanitizePath(simName, '-') + ".xml"))
				{
					XmlTextWriter writer = new XmlTextWriter(stream);
					writer.Formatting = Formatting.Indented;
					writer.WriteStartDocument();
					writer.WriteStartElement("RegionSettings");
					writer.WriteStartElement("General");
					writer.WriteElementString("AllowDamage", "False");
					writer.WriteElementString("AllowLandResell", "True");
					writer.WriteElementString("AllowLandJoinDivide", "True");
					writer.WriteElementString("BlockFly", "False");
					writer.WriteElementString("BlockLandShowInSearch", "True");
					writer.WriteElementString("BlockTerraform", "False");				
					writer.WriteElementString("DisableCollisions", "False");
					writer.WriteElementString("DisablePhysics", "False");
					writer.WriteElementString("DisableScripts", "False");
					// to do - we need a list of maturity ratings
					writer.WriteElementString("MaturityRating", (string)(sim.Access == SimAccess.PG ? "0" : "1"));
					writer.WriteElementString("RestrictPushing", "True");
					writer.WriteElementString("AgentLimit", "40");
					writer.WriteElementString("ObjectBonus", "1");
					writer.WriteEndElement();	// General

					writer.WriteStartElement("GroundTextures");
					writer.WriteElementString("Texture1", sim.TerrainBase0.ToString());
					writer.WriteElementString("Texture2", sim.TerrainBase1.ToString());
					writer.WriteElementString("Texture3", sim.TerrainBase2.ToString());
					writer.WriteElementString("Texture4", sim.TerrainBase3.ToString());
					writer.WriteElementString("ElevationLowSW", sim.TerrainHeightRange00.ToString());		
					writer.WriteElementString("ElevationLowNW", sim.TerrainHeightRange01.ToString());
					writer.WriteElementString("ElevationLowSE", sim.TerrainHeightRange10.ToString());
					writer.WriteElementString("ElevationLowNE", sim.TerrainHeightRange11.ToString());
					writer.WriteElementString("ElevationHighSW", sim.TerrainStartHeight00.ToString());
					writer.WriteElementString("ElevationHighNW", sim.TerrainStartHeight01.ToString());
					writer.WriteElementString("ElevationHighSE", sim.TerrainStartHeight10.ToString());
					writer.WriteElementString("ElevationHighNE", sim.TerrainStartHeight11.ToString());
					writer.WriteEndElement();	// GroundTextures

					writer.WriteStartElement("Terrain");
					writer.WriteElementString("WaterHeight", String.Format(CultureInfo.InvariantCulture, "{0:0.######}", sim.WaterHeight));
					writer.WriteElementString("TerrainRaiseLimit", "100");
					writer.WriteElementString("TerrainLowerLimit", "-100");
					writer.WriteElementString("UseEstateSun", "True");
					writer.WriteElementString("FixedSun", "False");
					writer.WriteEndElement();	// Terrain

					writer.WriteEndElement(); // RegionSettings
					writer.Flush();
				}
  			}
			catch (Exception ex)
			{
				Logger.Log(String.Format("Failed saving region info to {0}.xml: {1}", simName, ex.Message), Helpers.LogLevel.Error);
			}
		}
	}
}
